#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

struct catalog_elem {
	const char *out_name;
	long offset;
	long size;
};

struct x3catalog {
	struct catalog_elem *first;
	unsigned count;
};

static struct x3catalog *
parse_cat(char *disposable_text, struct x3catalog *out_cat)
{
	char *cptr = strchr(disposable_text, '\n');
	char *p2;
	unsigned i;
	long coffset = 0L;
	if (cptr == NULL)
		return NULL;
	out_cat->count = 0;
	++ cptr;
	while (1) {
		cptr = strchr(cptr, '\n');
		if (cptr == NULL)
			break;
		out_cat->count ++;
		++ cptr;
	}
	out_cat->first = (struct catalog_elem *)calloc(out_cat->count,
		sizeof(struct catalog_elem));
	cptr = strchr(disposable_text, '\n');
	cptr ++;
	for (i = 0; i < out_cat->count; ++ i) {
		out_cat->first[i].out_name = cptr;
		p2 = strchr(cptr, '\n');
		if (p2 == NULL) {
			free(out_cat->first);
			return NULL;
		}
		*p2 = '\0';
		cptr = strrchr(cptr, ' ');
		if (cptr == NULL) {
			free(out_cat->first);
			return NULL;
		}
		*cptr = '\0';
		cptr ++;
		out_cat->first[i].size = atoi(cptr);
		out_cat->first[i].offset = coffset;
		coffset += out_cat->first[i].size;
		cptr = p2 + 1;
	}
	return out_cat;
}

static char *
read_cat_text(const char *filename)
{
	char *textptr = NULL;
	FILE *f = fopen(filename, "r");
	struct stat s;
	long i;
	unsigned char mgk = 0xdb;
	if (stat(filename, &s) == -1)
		return NULL;
	textptr = (char *)malloc(s.st_size + 1);
	textptr[s.st_size] = '\0';
	if (f == NULL)
		return NULL;
	if (fread(textptr, 1, s.st_size, f) != (long unsigned)s.st_size) {
		free(textptr);
		fclose(f);
		return NULL;
	}
	fclose(f);
	for (i = 0; i < s.st_size; ++ i)
		textptr[i] ^= mgk ++;
	return textptr;
}

static char *
dup_cat_to_dat(const char *filepath)
{
	char *p = malloc(strlen(filepath) + 1);
	strcpy(p, filepath);
	p[strlen(p) - 3] = 'd';
	return p;
}

static void
dump_catalog(const struct x3catalog *c)
{
	unsigned i;
	for (i = 0; i < c->count; ++ i)
		printf("%u. %s, offset=%li, size=%li\n", i + 1,
			c->first[i].out_name,
			c->first[i].offset, c->first[i].size);
}

static void
show_help()
{
	puts("Operations:\n"
	"\td - dump catalog\n"
	"\tf <filter string> - filter records\n"
	"\th - help message\n"
	"\tx <record #> - extract file from dat archive\n"
	"\tq - quit\n");
}

static void
filter_catalog(const struct x3catalog *c, const char *fi)
{
	unsigned i;
	for (i = 0; i < c->count; ++ i)
		if (strstr(c->first[i].out_name, fi))
			printf("[filter=%s] %u. %s, offset=%li, size=%li\n",
					fi, i + 1,
					c->first[i].out_name,
					c->first[i].offset, c->first[i].size);
}

static int
xtract_from_dat(const char *catname, FILE **f,
		struct x3catalog *c, unsigned record_no)
{
	struct catalog_elem *target_elem;
	FILE *file_out;
	char *mem;
	char outfn[256];
	unsigned i;
	if (record_no > c->count || record_no < 1)
		return -1;
	if (*f == NULL) {
		char *datname = dup_cat_to_dat(catname);
		*f = fopen(datname, "r");
		free(datname);
		if (*f == NULL) 
			return -1;
	}
	target_elem = c->first + record_no - 1;
	fseek(*f, target_elem->offset, SEEK_SET);
	mem = malloc(target_elem->size);
	printf("[debug] malloc: %p, allocated %li byte(s)\n", mem,
			target_elem->size);
	printf("[debug] read %lu byte(s) to buffer\n",
			fread(mem, 1, target_elem->size, *f));
	for (i = 0; i < target_elem->size; ++ i)
		mem[i] ^= 0x33;
	sprintf(outfn, "record%u.out", record_no);
	file_out = fopen(outfn, "w");
	fwrite(mem, 1, target_elem->size, file_out);
	fclose(file_out);
	printf("[debug] saved to \"%s\"\n", outfn);
	return 0;
}

static void
cycle_input(struct x3catalog *c, const char *catfilename)
{
	char *inp_line = (char *)malloc(256);
	size_t n = 256UL;
	FILE *fdat = NULL;
	ssize_t chars_read;
	show_help();
	do {
		printf(" > ");
		fflush(stdout);
		chars_read = getline(&inp_line, &n, stdin);
		inp_line[chars_read - 1] = '\0';
		printf("[debug] answered => /*%s*/\n", inp_line);
		if (strcmp(inp_line, "d") == 0) {
			dump_catalog(c);
		}
		else if (strcmp(inp_line, "h") == 0) {
			show_help();
		}
		else if (inp_line[0] == 'f' && inp_line[1] == ' ') {
			filter_catalog(c, inp_line + 2);
		} else if (inp_line[0] == 'x' && inp_line[1] == ' ') {
			printf("extract %s\n", 
					xtract_from_dat(catfilename, &fdat, c,
					(unsigned)atoi(inp_line + 2)) == 0 ?
					"success" : "failure");
		}
	} while (strcmp(inp_line, "q") != 0);
	if (fdat != NULL)
		fclose(fdat);
	free(inp_line);
}

int
main(int argc, char **argv)
{
	char *disp_text;
	struct x3catalog xcat;
	if (argc < 2) {
		fprintf(stderr, "usage: %s <path to cat file>\n", argv[0]);
		return 0;
	}
	disp_text = read_cat_text(argv[1]);
	if (disp_text == NULL) {
		fprintf(stderr, "unable to read %s\n", argv[1]);
		return 1;
	}
	if (parse_cat(disp_text, &xcat) == NULL) {
		fputs("failed to parse input\n", stderr);
		free(disp_text);
		return 2;
	}
	cycle_input(&xcat, argv[1]);
	free(xcat.first);
	free(disp_text);
	return 0;
}
